﻿/****************
* Authored by Zhuo "Wizcas" Chen
* 2018 © Wubi.fun all rights reserved
*****************/

using UnityEngine;
using Wubi.Utils;

namespace Components.Cam
{
    [RequireComponent(typeof(Camera))]
    public class Orbit : MonoBehaviour
    {
        //public bool 
        [Header("自由视角设置")]
        public float sensitivity = 5f;
        public float minYaw = -360;
        public float maxYaw = 360;
        public float minPitch = -10;
        public float maxPitch = 10;
        public float minDistance = 1f;
        public float maxDistance = 5f;
        public int orbitMouseBtn = 1;

        [Header("运行时设置")]
        public float angularSpeed = 8;
        public float moveSpeed = 5;
        public Transform focus;
        public float origDistance;
        public bool autoOrbit;
        public Vector2 autoOrbitSpeed;
        public bool useUnscaledTime;
        [Tooltip("是否锁定视角")] public bool isLocked;

        #region Cam General Fields
        float _xAngle = 0f;
        float _yAngle = 0f;
        Quaternion _origRot; // 镜头初始旋转
        Quaternion _toRot; // 镜头的目标旋转（会在LateUpate中旋转到该值）
        float _overrideDistance = float.NaN;
        Vector3 _origFocusPos;
        Transform _overrideFocus;
        #endregion

        #region Orbiting Fields
        Quaternion _origRot2Focus; // 镜头相对于焦点（focus）的初始旋转，仅当镜头Orbit时才会使用
        Quaternion _toRot2Focus; // 镜头相对于焦点（focus）的目标旋转，仅当镜头Orbit时才会使用
        Vector3 _origPos; // 镜头初始位置，用于重置镜头
        Vector3 _toPos; // 镜头要移动到的位置，，仅当镜头Orbit时才会使用
        #endregion

        #region Other Runtime Fields
        Coroutine _cWaitToReset;
        #endregion

        #region Properties
        float Distance
        {
            get { return float.IsNaN(_overrideDistance) ? origDistance : _overrideDistance; }
        }
        float deltaTime { get { return useUnscaledTime ? Time.unscaledDeltaTime : Time.deltaTime; } }
        #endregion

        private void Start()
        {
            //初始化位置和旋转信息
            _origRot = _toRot = transform.localRotation;
            _origPos = _toPos = transform.position;
            if (focus != null)
            {
                _origRot2Focus = _toRot2Focus = Quaternion.FromToRotation(
                    focus.forward,
                    (focus.position - transform.position).normalized);
                origDistance = Vector3.Distance(focus.position, transform.position);
                _origFocusPos = focus.position;
            }
        }

        private void Update()
        {
            #region Zoom
            if (Input.mouseScrollDelta.y != 0f)
            {
                _overrideDistance = Mathf.Clamp(Distance - Input.mouseScrollDelta.y, minDistance, maxDistance);
            }
            #endregion

            Vector2 delta = Vector2.zero;
            if (orbitMouseBtn >= 0 && Input.GetMouseButton(orbitMouseBtn))
            {
                delta.x = Input.GetAxis("Mouse X");
                delta.y = Input.GetAxis("Mouse Y");
            }
            else if (autoOrbit)
            {
                delta = autoOrbitSpeed * deltaTime;
            }
            if (delta != Vector2.zero)
            {
                TurnHeadDelta(delta.x * sensitivity, delta.y * sensitivity, true);
            }
            ComputOrbitPos();
        }

        private void LateUpdate()
        {
            UpdateLookDir();
            if (focus != null)
            {
                UpdateLookPos();
            }
        }

        #region Computing
        void TurnHeadTo(Vector2 toAngles)
        {
            var deltaX = toAngles.x - _xAngle;
            var deltaY = toAngles.y - _yAngle;
            TurnHeadDelta(deltaX, deltaY, false);
        }

        void TurnHeadDelta(float deltaHorizontal, float deltaVertical, bool applyClamp)
        {
            _xAngle += deltaHorizontal;
            _yAngle += deltaVertical;

            if (applyClamp)
            {
                _xAngle = _xAngle.ClampAngle(minYaw, maxYaw);
                _yAngle = _yAngle.ClampAngle(minPitch, maxPitch);
            }

            Quaternion xRot = Quaternion.AngleAxis(_xAngle, Vector3.up);
            Quaternion yRot = Quaternion.AngleAxis(_yAngle, -Vector3.right);
            _toRot = xRot * yRot * _origRot;
            _toRot2Focus = xRot * yRot * _origRot2Focus;
        }

        void ComputOrbitPos()
        {
            var f = _overrideFocus ?? focus;
            var dir = _toRot2Focus * f.forward;
            _toPos = f.position + dir * -Distance;
            Debug.DrawRay(f.position, dir, Color.green);
            Debug.DrawLine(f.position, transform.position, Color.magenta);
        }
        #endregion

        #region Transforming
        void UpdateLookDir()
        {
            if (Quaternion.Angle(transform.localRotation, _toRot) >= .01f)
            {
                transform.localRotation = Quaternion.Slerp(
                    transform.localRotation,
                    _toRot,
                    angularSpeed * deltaTime);
            }
            else
            {
                transform.localRotation = _toRot;
            }
        }

        void UpdateLookPos()
        {
            transform.position = Vector3.Lerp(transform.position, _toPos, moveSpeed * deltaTime);
        }
        #endregion

        #region Public APIs
        [Header("分镜设置")]
        /// <summary>
        /// 预置角度，便于让镜头看向某个位置
        /// </summary>
        public Shot[] presetShots;

        /// <summary>
        /// 重置视角，看向初始位置，并取消覆盖的视角模式
        /// </summary>
        public void ResetLook()
        {
            autoOrbit = false;
            ApplyShot(new Shot(Vector2.zero), false);
            isLocked = false;
        }

        void StopResetCoroutine()
        {
            if (_cWaitToReset != null)
            {
                StopCoroutine(_cWaitToReset);
                _cWaitToReset = null;
            }
        }
        
        public void LookAtPreset(string name)
        {
            Shot? preset = null;
            if (!string.IsNullOrEmpty(name))
            {
                presetShots.Foreach(ps =>
                {
                    if (ps.name.Equals(name, System.StringComparison.OrdinalIgnoreCase))
                        preset = ps;
                });
            }
            if (!preset.HasValue)
            {
                ResetLook();
            }
            else
            {
                ApplyShot(preset.Value, true);
            }
        }


        /// <summary>
        /// 看向预置的角度
        /// </summary>
        /// <param name="index">预置值下标</param>
        /// <param name="overrideMode">视角模式，用于强制使用第一人称或焦点环绕来看向目标。若为NULL则表示使用lookMode字段的值</param>
        public void LookAtPreset(int index)
        {
            if (presetShots == null || index < 0 || index >= presetShots.Length) return;
            ApplyShot(presetShots[index], true);
        }
        /// <summary>
        /// 切换到指定的分镜头
        /// </summary>
        /// <param name="deltaAngles">目标角度相对与当前视角的偏移角度</param>
        /// <param name="overrideMode">视角模式，用于强制使用第一人称或焦点环绕来看向目标。若为NULL则表示使用lookMode字段的值</param>
        public void ApplyShot(Shot shot, bool lockView)
        {
            StopResetCoroutine();
            TurnHeadTo(shot.angles);
            _overrideDistance = shot.distance;
            _overrideFocus = shot.focus;
            isLocked = lockView;
        }

        public static Orbit Instance
        {
            get
            {
                if (Camera.main == null) return null;
                return Camera.main.GetComponent<Orbit>();
            }
        }

        public void BeforeGameStart()
        {
            autoOrbit = true;
        }

        public static void Invoke(System.Action<Orbit> action)
        {
            var instance = Instance;
            if (instance == null) return;
            if (action == null) return;
            action(instance);
        }

        #endregion

        /// <summary>
        /// 分镜头设置
        /// </summary>
        [System.Serializable]
        public struct Shot
        {
            public string name;
            public float distance;
            public Vector2 angles;
            public Transform focus;

            public Shot(string name, Vector2 angles, float distance, Transform focus)
            {
                this.name = name;
                this.angles = angles;
                this.distance = distance;
                this.focus = focus;
            }

            public Shot(Vector2 angles, float distance) : this("CamShot", angles, distance, null) { }

            public Shot(Vector2 angles) : this(angles, float.NaN) { }

            public bool isReset
            {
                get
                {
                    return angles == Vector2.zero && float.IsNaN(distance) && focus == null;
                }
            }
        }
    }
}
